﻿using System;
using System.Collections.Generic;
using System.Text;

namespace SqlServerDriver
{
	/*
		Generates code for use by EF under various configurations
		
		Sections
		The Class
		The Context
		Fluent configuration to include in context override method
		Fluent configuration class
		Sample code
		module template

		Scope
		1. Full: combine all
		2. Class only. Class + fluent config
		3. Class and sample. Class + fluent config + Sample

		Todo:
		Distinguish between fluent types.
		Order code gen
		Template dictionary?
	*/
	internal class EFClassGenerator
	{
		public EFClassGenerator(String TableName, SqlTableSchema schema)
		{
			m_SourceTableName = TableName;
			m_Schema = schema;
		}

		internal enum config {Annotations, FluentCentral, FluentIndividual}


		public String GenerateCode(String connectionString, int scope, config cfgOption)
		{
			// allow for schema 
			String[] tableParts = Helper.ParseTableName(m_SourceTableName);

			String tableName = tableParts[0];
			String schemaName = (tableParts.GetLength(0) == 2) ? tableParts[1] : "dbo";
			String efClassName = TableGeneratorHelpers.MakePascalCaseName(tableName, true);
			DbView.FieldInfo fi = m_Schema[0];
			// 0 = class name 
			// 1 = 1st field name
			// 2 = connection string 
			// 3 = generated class

			String fluent1Table = (cfgOption == config.FluentCentral) ? String.Format("        modelBuilder.Entity<{0}>().ToTable(\"{1}\",\"{2}\");", efClassName, tableName, schemaName) : "";
			String DbSetCode = String.Format("        public DbSet<{0}> {0}s {{get; set; }}\r\n", efClassName);

			String classCode = GenerateClassCode(efClassName, cfgOption);
			String sampleCode = String.Format(EF_SAMPLE_TEMPLATE, efClassName, TableGeneratorHelpers.MakePascalCaseName(fi.Name));
			//String fluentCode = GenerateFluentColumnCode(efClassName, cfgOption);
			String fluentCode = GenerateFluentCode(efClassName, cfgOption);
			String fluentClassCode = "";
			if (cfgOption == config.FluentIndividual)
			{
				fluentClassCode = fluentCode;
				fluentCode = String.Format(USE_ENTITY_CONFIGURATION_TEMPLATE, efClassName);
			}
			String contextCode = String.Format(CONTEXT_TEMPLATE, efClassName, connectionString.Replace("\\", "\\\\"), fluent1Table+"\r\n"+fluentCode, fluentClassCode);
			String moduleCode = String.Format(MODULE_TEMPLATE, classCode, sampleCode, connectionString.Replace("\\", "\\\\"), contextCode);
			
			switch (scope)
			{
				case 0: return String.Format("{0}{1}\r\n\r\n{2}",DbSetCode, fluentCode, classCode);
				case 1: return String.Format("{0}\r\n\r\n{1}{2}\r\n{3}",sampleCode, DbSetCode, fluentCode, classCode);
				case 2: return moduleCode;
			}
			return moduleCode;
		}

		public String GenerateClassCode(String efClassName, config cfgOption)
		{
			String Code = "";

			List<String> CodeLines = new List<String>();  // holds target Code 
			String[] tableParts = Helper.ParseTableName(m_SourceTableName);
			String tableName = tableParts[0];
			String tableAnnotation = "";
			if (tableParts.GetLength(0) > 1) // allow for schema
			{
				tableAnnotation = String.Format("[Table(\"{0}\", Schema=\"{1}\")]", tableParts[0], tableParts[1]);
			}
			else
			{
				tableAnnotation = String.Format("[Table(\"{0}\")]", tableParts[0]);
			}
			tableAnnotation = "    " + tableAnnotation + "\r\n";

			// lets define some templates
			// 0 = table name 1 = modified table name 2 = list of column
			// indentation assumes namespace 
			String FrameCode = @"
{3}    public partial class {1}
    {{
{2}
    }}
";
			// 0 = name 1 = type 2 = atts
			String ColumnTemplate =
@"{2}        public {1} {0} {{get; set;}}
";
			int keyCount = 0; // key order for data annotations
			for (int idx = 0; idx < m_Schema.Count; ++idx)
			{
				DbView.FieldInfo fi = m_Schema[idx];
				String columnAnnotation = (cfgOption == config.Annotations) ? MakeColumnParams(fi, keyCount) : "";
				// align
				if (columnAnnotation.Length > 0)
					columnAnnotation = "        "+columnAnnotation+"\r\n";
				String line = String.Format(ColumnTemplate, 
											TableGeneratorHelpers.MakePascalCaseName(fi.Name), 
											TableGeneratorHelpers.MakeTypeString(fi), 
											columnAnnotation);
				CodeLines.Add(line);
				if (fi.IsPKey) ++ keyCount; // for following key fields.
			}

			// assemble

			Code = String.Format(FrameCode, m_SourceTableName, 
			                                TableGeneratorHelpers.MakePascalCaseName(tableName, true), 
											String.Join("", CodeLines.ToArray()),
											tableAnnotation); 
			return Code;
		}

		public String GenerateFluentColumnCode(String className, config cfgOption)
		{
			// no fluent config if annotations
			if (cfgOption == config.Annotations) return "";

			List<String> CodeLines = new List<String>();  // holds target Code 

			// 0 = classname 1 = propertyname 2 = column name
			String fluent1ColumnTemplate = "          modelBuilder.Entity<{0}>().Property(p => p.{1}).HasColumnName(\"{2}\");";

			for (int idx = 0; idx < m_Schema.Count; ++idx)
			{
				DbView.FieldInfo fi = m_Schema[idx];
				String propertyName = TableGeneratorHelpers.MakePascalCaseName(fi.Name);
				if (propertyName != fi.Name)
					CodeLines.Add(String.Format(fluent1ColumnTemplate, className, propertyName, fi.Name));
			}
			return String.Join("\r\n", CodeLines.ToArray());
		}

		public String GenerateFluentCode(String className, config cfgOption)
		{
			// no fluent config if annotations
			if (cfgOption == config.Annotations) return "";
			
			// if central then we need an object otherwise its the config class
			String modelBuilder = (cfgOption == config.FluentCentral) ? String.Format("modelBuilder.Entity<{0}>()", className) : "this";

			// table
			String[] tableParts = Helper.ParseTableName(m_SourceTableName);
			String tableName = tableParts[0];
			String schemaName = (tableParts.GetLength(0) == 2) ? tableParts[1] : "dbo";
			String code = String.Format("        {0}.ToTable(\"{1}\",\"{2}\");\r\n", modelBuilder, tableName, schemaName);

			List<String> CodeLines = new List<String>();  // holds target Code 

			// 0 = classname 1 = propertyname 2 = column name
			String fluent1ColumnTemplate = "          {0}.Property(p => p.{1}).HasColumnName(\"{2}\");";

			for (int idx = 0; idx < m_Schema.Count; ++idx)
			{
				DbView.FieldInfo fi = m_Schema[idx];
				String propertyName = TableGeneratorHelpers.MakePascalCaseName(fi.Name);
				if (propertyName != fi.Name)
					CodeLines.Add(String.Format(fluent1ColumnTemplate, modelBuilder, propertyName, fi.Name));
			}
			code = code + String.Join("\r\n", CodeLines.ToArray());
			if (cfgOption == config.FluentCentral)
				return code;
			// embed in class
			return String.Format(ENTITY_CONFIGURATION_TEMPLATE, className, code);
		}

		/*
		1. Make all lower
		2. Capitalise 1st
		3. Capitalise all after _ space - 
		4. Remove _ space -
		*/

		private string MakeColumnParams(DbView.FieldInfo fi, int keyOrder)
		{
			List<String> columnParms = new List<String>();
			
			if (fi.IsPKey)
				columnParms.Add("Key");

			List<String> columnData = new List<String>();
			if (fi.Name != TableGeneratorHelpers.MakePascalCaseName(fi.Name))
				columnData.Add("\""+fi.Name+"\"");
			if (fi.IsPKey)
				columnData.Add("Order=" + keyOrder.ToString());
			
			if (columnData.Count > 0)
			{
				columnParms.Add("Column("+String.Join(", ", columnData.ToArray())+")");
			}
			if (fi.Type == "C")
				columnParms.Add("MaxLength(" + fi.Length.ToString() + ")");
			if (!fi.AllowsNulls)
				columnParms.Add("Required");
			if (columnParms.Count == 0) return ""; 
			return "["+String.Join(", ", columnParms.ToArray())+"]";
		}

		// [Column( IsPrimaryKey = true, IsDbGenerated = true )]

		String m_SourceTableName;
		SqlTableSchema m_Schema;

// 0 Class
// 1 Sample code
// 2. Connection String
// 3. Context class
		String MODULE_TEMPLATE = @"
using System;
using System.Linq;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations; // remove if using fluent configuration
using System.Data.Entity.ModelConfiguration; // remove if not using fluent configuration

namespace CodeGenTest
{{
    class Program
    {{
        ///////////////////////////////////////////////////////////////
        // Test code 
        static void Main(string[] args)
        {{
            TestClass(""{2}"");
        }}
{1}
    }}
    ///////////////////////////////////////////////////////////////
    // Database Context
{3}
    ///////////////////////////////////////////////////////////////
    // Generated class
{0}
    ///////////////////////////////////////////////////////////////
}}
";
		String EF_SAMPLE_TEMPLATE = @"
        private static void TestClass(String connectionString)
        {{
        	{0}DbContext db = new {0}DbContext(connectionString);
        	var result = (from o{0} in db.{0}s
                          select o{0}).Take(5);
        	foreach ({0} o{0} in result)
            {{
                Console.WriteLine(""{{0}}"", o{0}.{1});
            }}
        }}
";
	// 0. Table name
	// 1. Connection string
	// 2. Fluent configuration
	// 3. Individual class for configuration
		String CONTEXT_TEMPLATE = @"
    public class {0}DbContext : DbContext 
    {{
        /// <summary>
        /// Constructor with connection
        /// </summary>
        public {0}DbContext(String connStr) : base(connStr)
        {{
        }}

        public DbSet<{0}> {0}s {{get; set; }}

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {{
            // add fluent configuration here.
{2}
        }}
    }}
{3}
";
		String ENTITY_CONFIGURATION_TEMPLATE=@"
        public class {0}Configuration: EntityTypeConfiguration<{0}>
        {{
            public {0}Configuration()
            {{
{1}
            }}
        }}
";

		String USE_ENTITY_CONFIGURATION_TEMPLATE=@"
             modelBuilder.Configurations.Add(new {0}Configuration());
";
	}

}
